/*
 * $Id: BasicRuntimeCheckControllerMixin.java 1259 2007-01-09 14:23:47Z tcoupaye $
 * 
 * Behavior Protocols extensions for static and runtime checking
 * developed for the Julia implementation of Fractal.
 *
 * Copyright (C) 2006
 *    Formal Methods In Software Engineering Group
 *    Institute of Computer Science
 *    Academy of Sciences of the Czech Republic
 *
 * Copyright (C) 2006 France Telecom
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact: ft@nenya.ms.mff.cuni.cz
 * Authors: Vladimir Mencl <mencl@nenya.ms.mff.cuni.cz>
 *
 * Created on Dec 6, 2004
 *
 */

package org.objectweb.fractal.bpc.julia;

import java.util.Iterator;
import java.util.List;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.fractal.api.control.NameController;
import org.objectweb.fractal.api.factory.InstantiationException;
import org.objectweb.fractal.bpc.ProtocolViolationException;
import org.objectweb.fractal.bpc.RuntimeCheckController;
import org.objectweb.fractal.bpc.checker.FractalRuntimeChecker;
import org.objectweb.fractal.bpc.checker.parser.SyntaxErrorException;
import org.objectweb.fractal.julia.Controller;
import org.objectweb.fractal.julia.InitializationContext;


/**
 * Implements the {@link org.objectweb.fractal.bpc.RuntimeCheckController}
 * interface.
 * 
 * This controller maintains the method history log and a list of currently open
 * method calls. These lists are updated by the enterMethod and leaveMethod
 * calls.
 * 
 * The controller is responsible for setting up the itfInstanceName, itfIsClient
 * and itfInstanceRef properties of the RuntimeCheckInterceptor objects
 * monitoring interfaces of the component.
 * 
 * @author Vladimir Mencl
 *  
 */

public abstract class BasicRuntimeCheckControllerMixin implements RuntimeCheckController, Controller {

	private List runtimeCheckHistory = null; 
	private List runtimeCheckCurrentMethods = null;
	private List errorsFound = null;
	private FractalRuntimeChecker checker = null;
	private String rtcheckProtocol = null;
	private boolean foundError = false;
	private boolean isStopped;
	private int traceLength;
	private boolean foundNotAcceptingError = false;
	
	public static boolean recordErrors = !"false".equals(System.getProperty("fractal.protocols.rtcheck.recorderrors","true"));
	public static boolean throwErrors = !"false".equals(System.getProperty("fractal.protocols.rtcheck.throwerrors","false"));
	public static boolean stopOnError = !"false".equals(System.getProperty("fractal.protocols.rtcheck.stoponerror","true"));
	public static int recordTraces = Integer.parseInt(System.getProperty("fractal.protocols.rtcheck.recordtrace","-1"));
	
	public static int verbose = Integer.parseInt(System.getProperty("fractal.protocols.rtcheck.verbosity","1"));
	/*
	 * verbosity levels:
	 *   0 - print nothing
	 *   1 - print only protocol violations
	 *   2 - print protocol initialization and satisfaction
	 */

	private BasicRuntimeCheckControllerMixin() {
	}
	

	public void startFcRtcheck(String protocol) {
		rtcheckProtocol = protocol;
		foundError = false;
		foundNotAcceptingError = false;
		errorsFound.clear();
		runtimeCheckHistory.clear();
		runtimeCheckCurrentMethods.clear();
		
		if (verbose>=2)
			System.err.println("starting checker for component "+getFcRtcheckMyName()+" with protocol "+protocol);
		try {
		  checker = new FractalRuntimeChecker(protocol, true);
		} catch (SyntaxErrorException e) {
			e.printStackTrace();
			throw new RuntimeException("syntax error in protocol: "+protocol,e);
		}
	}
	
	NameController _this_weaveableOptNC;
	
	private String getFcRtcheckMyName() { // note: could require super controller and put together FQ names
		return _this_weaveableOptNC == null ? "unknown component" : _this_weaveableOptNC.getFcName();
	}

	public void stopFcRtcheck() {
		try {
		if (isStopped) System.err.println("rtcheck: "+getFcRtcheckMyName()+": checker is already stopped due to error(s) found.");
		else
		if ((checker != null))
			if (foundError) {
				if (verbose>=1)
					System.err.println("rtcheck: "+getFcRtcheckMyName()+": errors were found in execution trace");
			} else if (!checker.isAccepting()) {
				foundError=true;
				if (verbose>=1)
					System.err.println("rtcheck: "+getFcRtcheckMyName()+": protocol does not permit to stop here");
			} else
			{
				if (verbose>=2) {
					System.err.println("rtcheck: "+getFcRtcheckMyName()+": protocol satisfied");
					try {
						FileOutputStream file = new FileOutputStream("tmp/" + getFcRtcheckMyName() + "@" + this.hashCode() + ".html"); 
						checker.writeUsageFile(file);
						file.close();
						
					}
					catch (IOException e) {
							System.err.println("rtcheck: error writing protocol usage file");
					}
				}
			}

		} catch (ArrayIndexOutOfBoundsException e) { System.err.println("rtcheck: "+getFcRtcheckMyName()+" checker back-end puked: "+e); };
		if (foundError && recordErrors && (errorsFound.size()>0) && (verbose>=2)) {
			String errors[] = getFcStringEventList(errorsFound);
			System.err.println("Erroneous events [" + errors.length + "] recorded for component "+getFcRtcheckMyName());
	        for (int i=0;i<errors.length;i++) {
	                System.err.println(errors[i]);
	        };			
		}
		if (foundError && (recordTraces!=0) && (verbose>=2)) {
	        String calls[] = getFcMethodHistory();
			System.err.println("Trace [" + calls.length + " of " + traceLength + "] recorded for component "+getFcRtcheckMyName());
	        for (int i=0;i<calls.length;i++) {
	                System.err.println(calls[i]);
	        };
		}
	}

	/**
	 * @see org.objectweb.fractal.bpc.RuntimeCheckController#enterFcMethod(java.lang.String, java.lang.String, boolean, java.lang.Object[])
	 */
	public void enterFcMethod(String itfName, String methodName, boolean isClient, Object[] params) {
		recordMethodAction(itfName, methodName,isClient,params,true);
		synchronized (runtimeCheckCurrentMethods) {
			runtimeCheckCurrentMethods.add(new RuntimeCheckCurrentMethodEntry(itfName, methodName, isClient, params));
		}
	}

	/**
	 * @see org.objectweb.fractal.bpc.RuntimeCheckController#leaveFcMethod(java.lang.String, java.lang.String, boolean, java.lang.Object[])
	 */
	public void leaveFcMethod(String itfName, String methodName, boolean isClient, Object[] params) {
		recordMethodAction(itfName, methodName,isClient,params,false);
		
		boolean foundMatch = false;
		synchronized (runtimeCheckCurrentMethods) {
			for (int i=runtimeCheckCurrentMethods.size()-1; i>=0; i--) {
				if (((RuntimeCheckCurrentMethodEntry)runtimeCheckCurrentMethods.get(i)).methodName.equals(methodName)) {
					runtimeCheckCurrentMethods.remove(i);
					foundMatch = true;
					break;
				}
			}
		}
		if (!foundMatch) { 
			new Exception("leaving method "+methodName+" but entry not recorded").printStackTrace();
		}
	}
	
	public static void breakme() {
		
	}
	
	public void recordMethodAction(String itfName, String methodName, boolean isClient, Object[] params, boolean isEntry) {
		synchronized (runtimeCheckHistory) {
			if (!isStopped && (checker != null)) {
				String event = (isClient == isEntry ?"!":"?")+itfName + "." + methodName+(isEntry?"^":"$");
				if (verbose>=3) 
				  System.out.println("component "+getFcRtcheckMyName()+": processing event "+event);
				int eventStatus = checker.notify(event);
				if (eventStatus!=0) {
					breakme();
					foundError = true;
					if (stopOnError) {
						isStopped = true;
						checker = null;
					}
					if (recordErrors) {
					  errorsFound.add(new RuntimeCheckHistoryEntry(itfName,methodName,isClient,params,isEntry));
					}
					String msg = getFcRtcheckMyName()+": incorrect event "+event;
					ProtocolViolationException e = new ProtocolViolationException(msg);
					if (verbose>=1) {
						e.printStackTrace();
						//System.err.println(msg);
						if (recordTraces!=0) {
					        String calls[] = getFcMethodHistory();
							System.err.println("Trace ["+calls.length+" of " + traceLength + "] recorded for " + getFcRtcheckMyName() + " up to this point:");
					        for (int i=0;i<calls.length;i++) {
					                System.err.println(calls[i]);
					        };
						}
					};
					if (throwErrors)
						throw e;
				}
				
			}
			if (!isStopped && (recordTraces!=0)) {
				// not necessary: synced at outer scope synchronized (runtimeCheckHistory) {
					if ((recordTraces != -1 ) && (runtimeCheckHistory.size()>=recordTraces)) {
						runtimeCheckHistory.remove(0);
					}
					traceLength++;
					runtimeCheckHistory.add(new RuntimeCheckHistoryEntry(itfName, 
						methodName, isClient, params, isEntry));
				// }
			};
		}
	}

	public String[] getFcCurrentMethods() {
		java.util.ArrayList retVal = new java.util.ArrayList();
		
		synchronized (runtimeCheckCurrentMethods) {
			for (Iterator it=runtimeCheckCurrentMethods.iterator(); it.hasNext();) {
				RuntimeCheckCurrentMethodEntry i = (RuntimeCheckCurrentMethodEntry)it.next(); 
				retVal.add((i.isClient?"!":"?")+i.itfName + ":" + i.methodName);
			}
		}
		return (String[])retVal.toArray(new String[0]);
	}
	/**
	 * 
	 * @param eventList - must be a list of RuntimeCheckHistoryEntry elements
	 */
	private String[] getFcStringEventList(List eventList) {
		java.util.ArrayList retVal = new java.util.ArrayList();
		
		synchronized (eventList) {
			for (Iterator it=eventList.iterator(); it.hasNext();) {
				RuntimeCheckHistoryEntry i = (RuntimeCheckHistoryEntry)it.next(); 
				retVal.add((i.isEntry==i.isClient?"!":"?")+i.itfName + ":" + i.methodName+(i.isEntry?"^":"$"));
			}
		}
		return (String[])retVal.toArray(new String[0]);		
	}

	public String[] getFcMethodHistory() {
		return getFcStringEventList(runtimeCheckHistory);
	}
	
	public abstract void _super_initFcController(InitializationContext ic) throws InstantiationException;
	
	public void initFcController(InitializationContext ic) throws InstantiationException {
		_super_initFcController(ic);
		// RuntimeCheckInterceptorCodeGenerator.breakable(ic);
		runtimeCheckHistory = new java.util.Vector();
		runtimeCheckCurrentMethods = new java.util.Vector();
		errorsFound = new java.util.Vector();
		
		// set name for all interfaces
		// done by interface now
		/*
	    Object itfs[] = ic.interfaces.values().toArray(); // xxcComp.getFcInterfaces();
	    for (int i=0;i<itfs.length; i++) {
	    	// test if the impl of this interface is a RuntimeCheckInterceptor
	    	if (itfs[i] instanceof ComponentInterface) {
	    	  ComponentInterface itf = (ComponentInterface)itfs[i];
	    	  // note: interfaces may contain entries which are not an Interface (e.g., content)
	    	  Object impl = ((ComponentInterface)itf).getFcItfImpl();
	    	  while (impl instanceof Interceptor) {
	    		if ( impl instanceof RuntimeCheckInterceptor) {
	    			System.out.println("setting itfname for "+itf.getFcItfName());
	    			((RuntimeCheckInterceptor)impl).setItfInstanceName(itf.getFcItfName());
	    			((RuntimeCheckInterceptor)impl).setItfInstanceRef(itf);

	    			org.objectweb.fractal.api.Type itfType = itf.getFcItfType();	    			
	    			if (itfType instanceof org.objectweb.fractal.api.type.InterfaceType) {
	    				System.out.println("setting itfIsClient for "+itf.getFcItfName()+ " to "+((org.objectweb.fractal.api.type.InterfaceType)itfType).isFcClientItf());
	    				((RuntimeCheckInterceptor)impl).setItfIsClient(((org.objectweb.fractal.api.type.InterfaceType)itfType).isFcClientItf());
	    			}
	    		};
	    	    impl = ((Interceptor)impl).getFcItfDelegate();
	    	  }
	    	}
	    	
	    };
	    */
	    /* FIXME: name not set here during initFcController + name is not fully qualified 
	    try {
			if (Class.forName("java.lang.management.ManagementFactory") != null ) {
				String name = ((org.objectweb.fractal.api.control.NameController)ic.getInterface( "name-controller" )).getFcName();
				MBeanHelper.registerMBean(new RuntimeCheckMonitor(this), name);
			}
		} catch (ClassNotFoundException e) { } catch (InstantiationException e) {}
	    */
		// debug: new Exception("BasicRuntimeCheckController: intialized").printStackTrace();
	}

}
